/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description: UDK tailq source file
 * Author: -
 * Create: 2021.6.22
 */

#include <sys/queue.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "securec.h"
#include "udk_common.h"
#include "udk_mcfg.h"
#include "udk_log.h"
#include "udk.h"
#include "udk_tailq.h"

TAILQ_HEAD(udk_tailq_elem_head, udk_tailq_elem);
/* local tailq list */
static struct udk_tailq_elem_head g_udk_tailq_elem_head = TAILQ_HEAD_INITIALIZER(g_udk_tailq_elem_head);

/* number of tailqs registered, -1 before call to udk_tailqs_init */
static int g_udk_tailqs_count = -1;

static struct udk_tailq_head *udk_tailq_lookup(const char *name)
{
    uint32_t i;
    struct udk_mem_config *mcfg = udk_get_configuration()->mem_config;

    if (name == NULL) {
        return NULL;
    }

    for (i = 0; i < UDK_MAX_TAILQ; i++) {
        if (!strncmp(name, mcfg->tailq_head[i].name, UDK_TAILQ_NAMESIZE - 1)) {
            return &mcfg->tailq_head[i];
        }
    }

    return NULL;
}

static void udk_dump_tailq(FILE *f)
{
    struct udk_mem_config *mcfg;
    uint32_t i = 0;

    mcfg = udk_get_configuration()->mem_config;

    udk_mcfg_tailq_read_lock();
    for (i = 0; i < UDK_MAX_TAILQ; i++) {
        const struct udk_tailq_head *tailq = &mcfg->tailq_head[i];
        const struct udk_tailq_entry_head *head = &tailq->tailq_head;

        (void)fprintf(f, "Tailq %u: qname:<%s>, tqh_first:0x%lx, tqh_last:0x%lx\n",
            i, tailq->name, udk_get_ptr(head->tqh_first), udk_get_ptr(head->tqh_last));
    }
    udk_mcfg_tailq_read_unlock();
}

static struct udk_tailq_head *udk_tailq_create(const char *name)
{
    struct udk_tailq_head *head = NULL;

    if (!udk_tailq_lookup(name) && (g_udk_tailqs_count + 1 < UDK_MAX_TAILQ)) {
        struct udk_mem_config *mcfg;

        mcfg = udk_get_configuration()->mem_config;
        head = &mcfg->tailq_head[g_udk_tailqs_count];
        if (snprintf_s(head->name, sizeof(head->name), sizeof(head->name) - 1, "%s", name) < 0) {
            UDK_LOG(WARNING, COMMON, "print head name failed\n");
        }

        TAILQ_INIT(&head->tailq_head);
        g_udk_tailqs_count++;
    }

    return head;
}

/* local register, used to store "early" tailqs before udk_init() and to
 * ensure secondary process only registers tailqs once. */
static int udk_tailq_local_register(struct udk_tailq_elem *t)
{
    struct udk_tailq_elem *temp = NULL;

    TAILQ_FOREACH(temp, &g_udk_tailq_elem_head, next) {
        if (!strncmp(t->name, temp->name, sizeof(temp->name))) {
            return -1;
        }
    }

    TAILQ_INSERT_TAIL(&g_udk_tailq_elem_head, t, next);
    return 0;
}

static void udk_tailq_update(struct udk_tailq_elem *t)
{
    if (udk_process_type() == UDK_PROC_PRIMARY) {
        /* primary process is the only one that creates */
        t->head = udk_tailq_create(t->name);
    } else {
        t->head = udk_tailq_lookup(t->name);
    }
}

int udk_tailq_register(struct udk_tailq_elem *t)
{
    if (udk_tailq_local_register(t) < 0) {
        UDK_LOG(ERR, COMMON, "%s tailq is already registered\n", t->name);
        goto error;
    }

    /* if a register happens after udk_tailqs_init(), then we can update tailq head */
    if (g_udk_tailqs_count >= 0) {
        udk_tailq_update(t);
        if (t->head == NULL) {
            UDK_LOG(ERR, COMMON, "Cannot initialize tailq: %s\n", t->name);
            TAILQ_REMOVE(&g_udk_tailq_elem_head, t, next);
            goto error;
        }
    }

    return 0;

error:
    t->head = NULL;
    return -1;
}

int udk_tailqs_init(void)
{
    struct udk_tailq_elem *t = NULL;

    g_udk_tailqs_count = 0;

    TAILQ_FOREACH(t, &g_udk_tailq_elem_head, next) {
        /* second part of register job for "early" tailqs, see udk_tailq_register */
        udk_tailq_update(t);
        if (t->head == NULL) {
            UDK_LOG(ERR, COMMON, "Cannot initialize tailq: %s\n", t->name);
            /* TAILQ_REMOVE not needed, error is already fatal */
            goto fail;
        }
    }

    return 0;

fail:
    udk_dump_tailq(stderr);
    return -1;
}
